﻿// -----------------------------------------------------------------------
// Copyright (c) David Kean. All rights reserved.
// -----------------------------------------------------------------------
extern alias pcl;
using System;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using AsyncOperation = pcl::System.ComponentModel.AsyncOperation;
using AsyncOperationManager = pcl::System.ComponentModel.AsyncOperationManager;

namespace Portable.ComponentModel
{
    [TestClass]
    public class AsyncOperationTests
    {
        [TestMethod]
        public void OperationCompleted_WhenCompleted_ThrowsInvalidOperation()
        {
            var operation = CreateSynchronousAsyncOperation();
            operation.OperationCompleted();


            ExceptionAssert.Throws<InvalidOperationException>(() =>
            {
                operation.OperationCompleted();
            });
        }

        [TestMethod]
        public void Post_WhenCompleted_ThrowsInvalidOperation()
        {
            var operation = CreateSynchronousAsyncOperation();
            operation.OperationCompleted();

            ExceptionAssert.Throws<InvalidOperationException>(() =>
            {
                operation.Post((_) => { }, new object());
            });
        }

        [TestMethod]
        public void PostOperationCompleted_WhenCompleted_ThrowsInvalidOperation()
        {
            var operation = CreateSynchronousAsyncOperation();
            operation.OperationCompleted();

            ExceptionAssert.Throws<InvalidOperationException>(() =>
            {
                operation.PostOperationCompleted((_) => { }, new object());
            });
        }

        [TestMethod]
        public void OperationCompleted_CallsSynchronizationContextOperationCompleted()
        {
            int callCount = 0;
            var context = SynchronizationContextFactory.OverrideOperationCompleted(() => callCount++);
            var operation = CreateAsyncOperation(context);
            
            operation.OperationCompleted();

            Assert.AreEqual(callCount, 1);
        }

        [TestMethod]
        public void Post_PassesDToSynchronizationContextPost()
        {
            SendOrPostCallback result = null;
            var context = SynchronizationContextFactory.OverridePost((d, argument) => result = d);
            var operation = CreateAsyncOperation(context);

            SendOrPostCallback callback = (argument) => { };
            operation.Post(callback, (object)null);

            Assert.AreSame(callback, result);
        }

        [TestMethod]
        public void Post_PassesArgToSynchronizationContextPost()
        {
            object result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = a);
            var operation = CreateAsyncOperation(context);

            object argument = new object();
            operation.Post((_) => { }, argument);

            Assert.AreSame(argument, result);
        }

        [TestMethod]
        public void Post_NullAsD_PassesNullDToSynchronizationContextPost()
        {
            SendOrPostCallback result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = d);
            var operation = CreateAsyncOperation(context);

            operation.Post((SendOrPostCallback)null, new object());

            Assert.IsNull(result);
        }

        [TestMethod]
        public void Post_NullAsArg_PassesNullArgToSynchronizationContextPost()
        {
            object result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = a);
            var operation = CreateAsyncOperation(context);

            operation.Post((_) => { }, (object)null);

            Assert.IsNull(result);
        }

        [TestMethod]
        public void PostOperationCompleted_CallsSynchronizationContextOperationCompleted()
        {
            int callCount = 0;
            var context = SynchronizationContextFactory.OverrideOperationCompleted(() => callCount++);
            var operation = CreateAsyncOperation(context);

            operation.PostOperationCompleted((_) => { }, new object());

            Assert.AreEqual(callCount, 1);
        }


        [TestMethod]
        public void PostOperationCompleted_PassesDToSynchronizationContextPost()
        {
            SendOrPostCallback result = null;
            var context = SynchronizationContextFactory.OverridePost((d, argument) => result = d);
            var operation = CreateAsyncOperation(context);

            SendOrPostCallback callback = (argument) => { };
            operation.PostOperationCompleted(callback, (object)null);

            Assert.AreSame(callback, result);
        }

        [TestMethod]
        public void PostOperationCompleted_PassesArgToSynchronizationContextPost()
        {
            object result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = a);
            var operation = CreateAsyncOperation(context);

            object argument = new object();
            operation.PostOperationCompleted((_) => { }, argument);

            Assert.AreSame(argument, result);
        }

        [TestMethod]
        public void PostOperationCompleted_NullAsD_PassesNullDToSynchronizationContextPost()
        {
            SendOrPostCallback result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = d);
            var operation = CreateAsyncOperation(context);

            operation.PostOperationCompleted((SendOrPostCallback)null, new object());

            Assert.IsNull(result);
        }

        [TestMethod]
        public void PostOperationCompleted_NullAsArg_PassesNullArgToSynchronizationContextPost()
        {
            object result = null;
            var context = SynchronizationContextFactory.OverridePost((d, a) => result = a);
            var operation = CreateAsyncOperation(context);

            operation.PostOperationCompleted((_) => { }, (object)null);

            Assert.IsNull(result);
        }

        private static AsyncOperation CreateSynchronousAsyncOperation()
        {
            var context = SynchronizationContextFactory.OverridePost((d, state) => d(state));

            return CreateAsyncOperation(context);
        }

        private static AsyncOperation CreateAsyncOperation(SynchronizationContext context)
        {
            return AsyncOperationManager.CreateOperation(context, (object)null);
        }
    }
}
